//
//  Created by Jesse Squires
//  http://www.jessesquires.com
//
//
//  Documentation
//  http://cocoadocs.org/docsets/JSQMessagesViewController
//
//
//  GitHub
//  https://github.com/jessesquires/JSQMessagesViewController
//
//
//  License
//  Copyright (c) 2014 Jesse Squires
//  Released under an MIT license: http://opensource.org/licenses/MIT
//

#import "JSQMessagesViewController.h"
#import "JSQMapViewController.h"
#import "JSQMessagesCollectionViewFlowLayoutInvalidationContext.h"
#import "JSQMessage.h"
#import "JSQMessageData.h"
#import "JSQMessageMediaData.h"
#import "JSQMessageBubbleImageDataSource.h"
#import "JSQMessageAvatarImageDataSource.h"
#import "JSQMessagesCollectionViewCellIncoming.h"
#import "JSQMessagesCollectionViewCellOutgoing.h"
#import "JSQMessagesTypingIndicatorFooterView.h"
#import "JSQMessagesLoadEarlierHeaderView.h"
#import "JSQMessagesToolbarContentView.h"
#import "JSQMessagesInputToolbar.h"
#import "JSQMessagesComposerTextView.h"
#import "JSQMessagesTimestampFormatter.h"
#import "NSString+JSQMessages.h"
#import "UIColor+JSQMessages.h"
#import "UIDevice+JSQMessages.h"
#import "NSBundle+JSQMessages.h"
#import "UIImage+JSQMessages.h"
#import "JSQVoiceStateView.h"
#import "NSAttributedString+Emotion.h"
#import <Photos/Photos.h>
#import <AssetsLibrary/AssetsLibrary.h>
#import "JSQMessagesEnum.h"
#import "JSQMessagesCollectionViewCellIncoming.h"
#import "JSQMessagesCollectionViewCellOutgoing.h"
#import "JSQMessagesCollectionViewCellNotice.h"
#import "JSQMessagesCollectionViewCellVoiceIncoming.h"
#import "JSQMessagesCollectionViewCellVoiceOutgoing.h"
#import "JSQAudioVoice.h"
#import "JSQMessageTools.h"
#import "JSQLocationMediaItem.h"



static void *kJSQMessagesKeyValueObservingContext = &kJSQMessagesKeyValueObservingContext;



@interface JSQMessagesViewController () <JSQMessagesInputToolbarDelegate,
                                         JSQMessagesKeyboardControllerDelegate, JSQVoiceViewDelegate, JSQEmoticonViewDelegate, JSQCategoryViewDelegate, JSQMessagesCollectionViewFlowLayoutFontDelegate>

@property (weak, nonatomic) IBOutlet JSQMessagesCollectionView *collectionView;
@property (weak, nonatomic) IBOutlet JSQMessagesInputToolbar *inputToolbar;
@property (weak, nonatomic) IBOutlet JSQMessageFunctionView *messageFunctionView;
@property (weak, nonatomic) IBOutlet UIView *coverView;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *toolbarHeightConstraint;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *toolbarBottomLayoutGuide;
@property (weak, nonatomic) UIView *snapshotView;
@property (assign, nonatomic) BOOL jsq_isObserving;
@property (strong, nonatomic) NSIndexPath *selectedIndexPathForMenu;
@property (weak, nonatomic) UIGestureRecognizer *currentInteractivePopGestureRecognizer;
@property (assign, nonatomic) BOOL textViewWasFirstResponderDuringInteractivePop;
@property (nonatomic, strong) JSQVoiceStateView *voiceStateView;
@property (nonatomic, strong) JSQAudioVoice *audioVoice;
@property (nonatomic, strong) JSQMessageModel *voiceModel;



- (void)jsq_configureMessagesViewController;
- (NSString *)jsq_currentlyComposedMessageText;
- (void)jsq_handleDidChangeStatusBarFrameNotification:(NSNotification *)notification;
- (void)jsq_didReceiveMenuWillShowNotification:(NSNotification *)notification;
- (void)jsq_didReceiveMenuWillHideNotification:(NSNotification *)notification;
- (void)jsq_updateKeyboardTriggerPoint;
- (void)jsq_setToolbarBottomLayoutGuideConstant:(CGFloat)constant;
- (void)jsq_handleInteractivePopGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer;
- (BOOL)jsq_inputToolbarHasReachedMaximumHeight;
- (void)jsq_adjustInputToolbarForComposerTextViewContentSizeChange:(CGFloat)dy;
- (void)jsq_adjustInputToolbarHeightConstraintByDelta:(CGFloat)dy;
- (void)jsq_scrollComposerTextViewToBottomAnimated:(BOOL)animated;
- (void)jsq_updateCollectionViewInsets;
- (void)jsq_setCollectionViewInsetsTopValue:(CGFloat)top bottomValue:(CGFloat)bottom;
- (BOOL)jsq_isMenuVisible;
- (void)jsq_addObservers;
- (void)jsq_removeObservers;
- (void)jsq_registerForNotifications:(BOOL)registerForNotifications;
- (void)jsq_addActionToInteractivePopGestureRecognizer:(BOOL)addAction;

@end



@implementation JSQMessagesViewController

#pragma mark - Class methods

+ (UINib *)nib {
    return [UINib nibWithNibName:NSStringFromClass([JSQMessagesViewController class])
                          bundle:[NSBundle bundleForClass:[JSQMessagesViewController class]]];
}

+ (instancetype)messagesViewController {
    return [[[self class] alloc] initWithNibName:NSStringFromClass([JSQMessagesViewController class])
                                          bundle:[NSBundle bundleForClass:[JSQMessagesViewController class]]];
}

#pragma mark - Initialization

- (void)jsq_configureMessagesViewController {
    self.view.backgroundColor = [UIColor whiteColor];

    self.jsq_isObserving = NO;
    self.toolbarHeightConstraint.constant = self.inputToolbar.preferredDefaultHeight;
    self.collectionView.dataSource = self;
    self.collectionView.delegate = self;
    self.collectionView.collectionViewLayout.flowLayoutFontDelegate = self;
    self.messageFunctionView.voiceView.delegate = self;
    self.messageFunctionView.emoticonView.delegate = self;
    self.messageFunctionView.categoryView.delegate = self;



    self.inputToolbar.delegate = self;
    self.inputToolbar.contentView.textView.placeHolder = [NSBundle jsq_localizedStringForKey:@"new_message"];
    self.inputToolbar.contentView.textView.delegate = self;
    self.automaticallyScrollsToMostRecentMessage = YES;

    // NOTE: let this behavior be opt-in for now
    // [JSQMessagesCollectionViewCell registerMenuAction:@selector(delete:)];

    self.showTypingIndicator = NO;
    self.topContentAdditionalInset = 0.0f;
    self.offsetHeight = 0.0f;
    [self jsq_updateCollectionViewInsets];

    // Don't set keyboardController if client creates custom content view via -loadToolbarContentView
    if (self.inputToolbar.contentView.textView != nil) {
        self.keyboardController = [[JSQMessagesKeyboardController alloc] initWithTextView:self.inputToolbar.contentView.textView
                                                                              contextView:self.view
                                                                     panGestureRecognizer:self.collectionView.panGestureRecognizer
                                                                                 delegate:self];
    }

    NSArray *nibViews = [[NSBundle bundleForClass:[JSQVoiceStateView class]] loadNibNamed:NSStringFromClass([JSQVoiceStateView class]) owner:nil options:nil];
    self.voiceStateView = (JSQVoiceStateView *)nibViews.firstObject;
    [self.coverView addSubview:self.voiceStateView];

    self.voiceStateView.translatesAutoresizingMaskIntoConstraints = NO;

    [self.coverView addConstraint:[NSLayoutConstraint constraintWithItem:self.voiceStateView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.voiceStateView attribute:NSLayoutAttributeWidth multiplier:1 constant:0]];
    [self.coverView addConstraint:[NSLayoutConstraint constraintWithItem:self.voiceStateView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.coverView attribute:NSLayoutAttributeWidth multiplier:0.5 constant:0]];
    [self.coverView addConstraint:[NSLayoutConstraint constraintWithItem:self.voiceStateView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.coverView attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]];
    [self.coverView addConstraint:[NSLayoutConstraint constraintWithItem:self.voiceStateView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.coverView attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]];


    [JSQMessagesCollectionViewCell registerMenuAction:@selector(customDelete:forItemAtIndexPath:)];
    [JSQMessagesCollectionViewCell registerMenuAction:@selector(collection:forItemAtIndexPath:)];
    [JSQMessagesCollectionViewCell registerMenuAction:@selector(forward:forItemAtIndexPath:)];
    [JSQMessagesCollectionViewCell registerMenuAction:@selector(refresh:forItemAtIndexPath:)];
    [JSQMessagesCollectionViewCell registerMenuAction:@selector(customCopy:)];

    [UIMenuController sharedMenuController].menuItems = @[[[UIMenuItem alloc] initWithTitle:@"复制" action:@selector(customCopy:)],
                                                          [[UIMenuItem alloc] initWithTitle:@"删除" action:@selector(customDelete:forItemAtIndexPath:)],
                                                          [[UIMenuItem alloc] initWithTitle:@"收藏" action:@selector(collection:forItemAtIndexPath:)],
                                                          [[UIMenuItem alloc] initWithTitle:@"转发" action:@selector(forward:forItemAtIndexPath:)],
                                                          [[UIMenuItem alloc] initWithTitle:@"刷新" action:@selector(refresh:forItemAtIndexPath:)]
    ];

    _audioAnimation = [JSQAudioPlaybackAnimation shareAudioPlaybackAnimation];
}

- (JSQMessageModel *)createBasicMessages {
    JSQMessageModel *messageModel = [[JSQMessageModel alloc] init];
    messageModel.senderId = self.senderId;
    messageModel.senderDisplayName = self.senderDisplayName;
    messageModel.messageID = self.sessionId;
    messageModel.date = [NSDate date];
    messageModel.messageStatus = MESSAGE_STATE_NOTSEND;

    return messageModel;
}

- (void)dealloc {
    [self jsq_registerForNotifications:NO];
    [self jsq_removeObservers];

    _collectionView.dataSource = nil;
    _collectionView.delegate = nil;
    _collectionView = nil;

    _messageFunctionView = nil;

    _inputToolbar.contentView.textView.delegate = nil;
    _inputToolbar.delegate = nil;
    _inputToolbar = nil;

    _toolbarHeightConstraint = nil;
    _toolbarBottomLayoutGuide = nil;

    _sessionId = nil;
    _sessionName = nil;
    _senderId = nil;
    _senderDisplayName = nil;

    [_keyboardController endListeningForKeyboard];
    _keyboardController = nil;
}

#pragma mark - Setters

- (void)setShowTypingIndicator:(BOOL)showTypingIndicator {
    if (_showTypingIndicator == showTypingIndicator) {
        return;
    }

    _showTypingIndicator = showTypingIndicator;
    [self.collectionView.collectionViewLayout invalidateLayoutWithContext:[JSQMessagesCollectionViewFlowLayoutInvalidationContext context]];
    [self.collectionView.collectionViewLayout invalidateLayout];
}

- (void)setShowLoadEarlierMessagesHeader:(BOOL)showLoadEarlierMessagesHeader {
    if (_showLoadEarlierMessagesHeader == showLoadEarlierMessagesHeader) {
        return;
    }

    _showLoadEarlierMessagesHeader = showLoadEarlierMessagesHeader;
    [self.collectionView.collectionViewLayout invalidateLayoutWithContext:[JSQMessagesCollectionViewFlowLayoutInvalidationContext context]];
    [self.collectionView.collectionViewLayout invalidateLayout];
    [self.collectionView reloadData];
}

- (void)setTopContentAdditionalInset:(CGFloat)topContentAdditionalInset {
    _topContentAdditionalInset = topContentAdditionalInset;
    [self jsq_updateCollectionViewInsets];
}

- (void)voiceState:(JSQVoiceStateType)voiceStateType {
    self.voiceStateView.voiceStateType = voiceStateType;

    if (!_voiceModel) {
        _voiceModel = [self createBasicMessages];
        _voiceModel.messageType = MESSAGE_TYPE_VOICE;
    }

    switch (voiceStateType) {
    case JSQVoiceStateBegin: {
        self.coverView.hidden = NO;

        if (_audioVoice) {
            _audioVoice.voice = nil;
            _audioVoice = nil;
        }

        NSString *voicePath = [JSQMessageTools voicePath:[JSQMessageTools getCurrentSystemDateSecond]];
        _audioVoice = [[JSQAudioVoice alloc] initWithFilePath:voicePath];
        __weak typeof(self) VC = self;
        _audioVoice.voice = ^(CGFloat change, CGFloat currentTime) {
            __strong typeof(VC) SVC = VC;
            NSLog(@"Voice ::  change : %f,  currentTime : %f", change, currentTime);
            NSString *imageName = [NSString stringWithFormat:@"yinjie（%@）", @((int)(change / 10)).stringValue];
            SVC.voiceStateView.InstructionsImageView.image = [UIImage jsq_bubbleIndicateImage:imageName];

            if (currentTime > MaximumRecordingSeconds) {
                [SVC.audioVoice over];
                SVC.coverView.hidden = YES;
                SVC.voiceModel.fileSize = [JSQMessageTools fileSizeWithByteString:SVC.voiceModel.fileLocalPath];
                [SVC sendVoice:SVC.voiceModel];
                _voiceModel = nil;
            } else {
                SVC.voiceModel.mediaDuration = [NSString stringWithFormat:@"%.1f", currentTime];
            }
        };
        _voiceModel.fileName = _audioVoice.fileName;
        _voiceModel.fileLocalPath = _audioVoice.filePath;

        [_audioVoice start];
        break;
    }
    case JSQVoiceStateSend: {
        self.coverView.hidden = YES;
        [_audioVoice over];
        _voiceModel.fileSize = [JSQMessageTools fileSizeWithByteString:_voiceModel.fileLocalPath];
        if ([_voiceModel.mediaDuration floatValue] > MinimumRecordingSeconds) {
            [self sendVoice:_voiceModel];
        }
        _voiceModel = nil;
        break;
    }
    case JSQVoiceStateCancel: {
        self.coverView.hidden = YES;
        [_audioVoice cancel];
        _voiceModel = nil;
        break;
    }
    case JSQVoiceStateMoveIn: {

        break;
    }
    case JSQVoiceStateMoveOuter: {

        break;
    }
    case JSQVoiceStateFailed: {
        self.coverView.hidden = YES;
        [_audioVoice cancel];
        _voiceModel = nil;
        break;
    }
    case JSQVoiceStateShortTime: {

        self.coverView.hidden = YES;
        [_audioVoice cancel];
        _voiceModel = nil;
        break;
    }
    default:
        break;
    }
}

- (void)selectTheFace:(NSString *)face {
    NSAttributedString *attributedText = _inputToolbar.contentView.textView.attributedText;
    if (attributedText && ![attributedText.string isEqualToString:@""]) {
        NSMutableAttributedString *attributed = [[NSMutableAttributedString alloc] initWithAttributedString:_inputToolbar.contentView.textView.attributedText];
        [attributed appendAttributedString:[[NSAttributedString alloc] initWithString:face]];
        _inputToolbar.contentView.textView.attributedText = attributed;
    } else {
        NSString *text = [_inputToolbar.contentView.textView.text stringByAppendingString:face];
        NSMutableAttributedString *attributed = [[NSMutableAttributedString alloc] initWithString:text];
        _inputToolbar.contentView.textView.attributedText = attributed;
    }
}

- (void)messageFunctionViewCategoryType:(NSInteger)item {

}

- (void)selectedCategoryType:(NSInteger)categoryType {
    [self messageFunctionViewCategoryType:categoryType];
}

#pragma mark - View lifecycle

- (void)viewDidLoad {
    [super viewDidLoad];

    [[[self class] nib] instantiateWithOwner:self options:nil];

    [self jsq_configureMessagesViewController];
    [self jsq_registerForNotifications:YES];

    __weak typeof(self) VC = self;
    self.unreadMessagesView.touch = ^(JSQNewMessageView *messageView){
        __weak typeof(VC) SVC = VC;
        messageView.hidden = YES;
        [SVC scrollToBottomAnimated:YES];
        [SVC finishReceivingMessageAnimated:YES];
    };
}

- (void)viewWillAppear:(BOOL)animated {
    NSParameterAssert(self.senderId != nil);
    NSParameterAssert(self.sessionId != nil);
    NSParameterAssert(self.sessionName != nil);
    [self jsq_addObservers];
    
    self.title = self.sessionName;
    [super viewWillAppear:animated];
    [self.view layoutIfNeeded];
    [self.collectionView.collectionViewLayout invalidateLayout];

    [self jsq_updateKeyboardTriggerPoint];

    if (self.disable) {
        self.inputToolbar.userInteractionEnabled = NO;
        self.inputToolbar.contentView.textView.placeHolder = [NSBundle jsq_localizedStringForKey:@"disable_placeHolder"];
    } else {
        self.inputToolbar.userInteractionEnabled = YES;
        self.inputToolbar.contentView.textView.placeHolder = [NSBundle jsq_localizedStringForKey:@"new_message"];
    }
    self.inputToolbar.contentView.textView.disable = YES;
    [self.messageFunctionView setNeedsUpdateConstraints];
    
}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    
    [self jsq_addActionToInteractivePopGestureRecognizer:YES];
    [self.keyboardController beginListeningForKeyboard];
    self.inputToolbar.contentView.textView.disable = NO;
    if ([UIDevice jsq_isCurrentDeviceBeforeiOS8]) {
        [self.snapshotView removeFromSuperview];
    }
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    
    [self.audioAnimation stop];
    self.collectionView.collectionViewLayout.springinessEnabled = NO;
    [self inputToolbarReset];
    [self jsq_removeObservers];
}

- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];
    
    [self jsq_addActionToInteractivePopGestureRecognizer:NO];
    [self.keyboardController endListeningForKeyboard];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    NSLog(@"MEMORY WARNING: %s", __PRETTY_FUNCTION__);
}

#pragma mark - View rotation

- (BOOL)shouldAutorotate {
    return YES;
}

- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone) {
        return UIInterfaceOrientationMaskAllButUpsideDown;
    }
    return UIInterfaceOrientationMaskAll;
}

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
    [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
    [self.collectionView.collectionViewLayout invalidateLayoutWithContext:[JSQMessagesCollectionViewFlowLayoutInvalidationContext context]];
}

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
    [super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
    if (self.showTypingIndicator) {
        self.showTypingIndicator = NO;
        self.showTypingIndicator = YES;
        [self.collectionView reloadData];
    }
}

- (void)customCopy:(id<JSQMessageData>)messageData {

}

- (void)customDelete:(id<JSQMessageData>)messageData forItemAtIndexPath:(NSIndexPath *)indexPath {

}

- (void)collection:(id<JSQMessageData>)messageData forItemAtIndexPath:(NSIndexPath *)indexPath {

}

- (void)forward:(id<JSQMessageData>)messageData forItemAtIndexPath:(NSIndexPath *)indexPath {

}

- (void)refresh:(id<JSQMessageData>)messageData forItemAtIndexPath:(NSIndexPath *)indexPath {

}

- (void)inputToolbarReset {
    [self setToolbarBottomLayoutGuideConstant:0];
    [self.inputToolbar.contentView.leftBarButtonItem setImage:[UIImage jsq_bubbleVoiceImage] forState:UIControlStateNormal];
    [self.inputToolbar.contentView.rightFaceBarButtonItem setImage:[UIImage jsq_bubbleFaceImage] forState:UIControlStateNormal];
    [self.inputToolbar.contentView.rightOtherBarButtonItem setImage:[UIImage jsq_bubbleOtherImage] forState:UIControlStateNormal];
    self.inputToolbar.contentView.textView.hidden = NO;
    self.messageFunctionView.messageFunctionType = JSQMessageFunctionEmotion;
}

#pragma mark - Messages view controller

- (void)didPressSendButton:(UIButton *)button withMessageModel:(JSQMessageModel *)messageModel withMessageMediaData:(id<JSQMessageMediaData>)messageMediaData {
    NSAssert(NO, @"Error! required method not implemented in subclass. Need to implement %s", __PRETTY_FUNCTION__);
}

- (void)didPressLeftAccessoryButton:(UIButton *)sender {
    if (self.messageFunctionView.messageFunctionType == JSQMessageFunctionVoice) {
        self.inputToolbar.contentView.textView.hidden = NO;
        [self.inputToolbar.contentView.textView becomeFirstResponder];
        [self.inputToolbar.contentView.leftBarButtonItem setImage:[UIImage jsq_bubbleVoiceImage] forState:UIControlStateNormal];
        self.messageFunctionView.messageFunctionType = JSQMessageFunctionNone;
    } else {
        [self setToolbarBottomLayoutGuideConstant:175];
        self.inputToolbar.contentView.textView.hidden = YES;
        [self.inputToolbar.contentView.leftBarButtonItem setImage:[UIImage jsq_bubbleKeyImage] forState:UIControlStateNormal];
        [self.inputToolbar.contentView.rightFaceBarButtonItem setImage:[UIImage jsq_bubbleFaceImage] forState:UIControlStateNormal];
        [self.inputToolbar.contentView.rightOtherBarButtonItem setImage:[UIImage jsq_bubbleOtherImage] forState:UIControlStateNormal];
        [self.messageFunctionView showFunctionType:JSQMessageFunctionVoice];
    }
}

- (void)didPressRightFaceAccessoryButton:(UIButton *)sender {
    if (self.messageFunctionView.messageFunctionType == JSQMessageFunctionEmotion) {
        self.inputToolbar.contentView.textView.hidden = NO;
        [self.inputToolbar.contentView.textView becomeFirstResponder];
        [self.inputToolbar.contentView.rightFaceBarButtonItem setImage:[UIImage jsq_bubbleFaceImage] forState:UIControlStateNormal];
        self.messageFunctionView.messageFunctionType = JSQMessageFunctionNone;
    } else {
        [self setToolbarBottomLayoutGuideConstant:175];
        self.inputToolbar.contentView.textView.hidden = NO;
        [self.inputToolbar.contentView.rightFaceBarButtonItem setImage:[UIImage jsq_bubbleKeyImage] forState:UIControlStateNormal];
        [self.inputToolbar.contentView.leftBarButtonItem setImage:[UIImage jsq_bubbleVoiceImage] forState:UIControlStateNormal];
        [self.inputToolbar.contentView.rightOtherBarButtonItem setImage:[UIImage jsq_bubbleOtherImage] forState:UIControlStateNormal];
        [self.messageFunctionView showFunctionType:JSQMessageFunctionEmotion];
    }
}

- (void)didPressRightOtherAccessoryButton:(UIButton *)sender {
    if (self.messageFunctionView.messageFunctionType == JSQMessageFunctionOther) {
        self.inputToolbar.contentView.textView.hidden = NO;
        [self.inputToolbar.contentView.textView becomeFirstResponder];
        [self.inputToolbar.contentView.rightOtherBarButtonItem setImage:[UIImage jsq_bubbleOtherImage] forState:UIControlStateNormal];
        self.messageFunctionView.messageFunctionType = JSQMessageFunctionNone;
    } else {
        [self setToolbarBottomLayoutGuideConstant:175];
        self.inputToolbar.contentView.textView.hidden = NO;
        [self.inputToolbar.contentView.rightOtherBarButtonItem setImage:[UIImage jsq_bubbleKeyImage] forState:UIControlStateNormal];
        [self.inputToolbar.contentView.leftBarButtonItem setImage:[UIImage jsq_bubbleVoiceImage] forState:UIControlStateNormal];
        [self.inputToolbar.contentView.rightFaceBarButtonItem setImage:[UIImage jsq_bubbleFaceImage] forState:UIControlStateNormal];
        [self.messageFunctionView showFunctionType:JSQMessageFunctionOther];
    }
}

- (NSInteger)categoryView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return [self messageFunctionView:collectionView numberOfItemsInSection:section];
}

- (void)categoryView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath categoryImageView:(UIImageView *)categoryImageView categoryLabel:(UILabel *)categoryLabel {
    [self messageFunctionView:collectionView cellForItemAtIndexPath:indexPath categoryImageView:categoryImageView categoryLabel:categoryLabel];
}

- (NSInteger)messageFunctionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return 0;
}

- (void)messageFunctionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath categoryImageView:(UIImageView *)categoryImageView categoryLabel:(UILabel *)categoryLabel {

}

- (void)finishSendingMessage {
    [self finishSendingMessageAnimated:YES];
}

- (void)finishSendingMessageAnimated:(BOOL)animated {

    self.inputToolbar.contentView.textView.text = nil;
    [self.inputToolbar.contentView.textView.undoManager removeAllActions];

    
    [self jsq_adjustInputToolbarForComposerTextViewContentSizeChange:self.inputToolbar.preferredDefaultHeight - CGRectGetHeight(self.inputToolbar.frame)];
    [[NSNotificationCenter defaultCenter] postNotificationName:UITextViewTextDidChangeNotification object:self.inputToolbar.contentView.textView];

    [self.collectionView.collectionViewLayout invalidateLayoutWithContext:[JSQMessagesCollectionViewFlowLayoutInvalidationContext context]];
    [self.collectionView reloadData];

    if (self.automaticallyScrollsToMostRecentMessage) {
        [self scrollToBottomAnimated:animated];
    }
}

- (void)finishReceivingMessage {
    [self finishReceivingMessageAnimated:YES];
}

- (void)finishReceivingMessageAnimated:(BOOL)animated {

    self.showTypingIndicator = NO;

    [self.collectionView.collectionViewLayout invalidateLayoutWithContext:[JSQMessagesCollectionViewFlowLayoutInvalidationContext context]];
    [self.collectionView reloadData];

    if (self.automaticallyScrollsToMostRecentMessage && ![self jsq_isMenuVisible]) {
        [self scrollToBottomAnimated:animated];
    }
}

- (void)reSending:(NSIndexPath *)indexPath {

}

- (MenuType)collectionViewMenu:(JSQMessagesCollectionView *)collectionView messageBubbleAtIndexPath:(NSIndexPath *)indexPath {
    return MenuTypeNone;
}

- (UIFont *)messageBubbleFontAtMessageData:(id<JSQMessageData>)messageData atIndexPath:(NSIndexPath *)indexPath {
    return [UIFont systemFontOfSize:16];
}

- (void)scrollToBottomAnimated:(BOOL)animated {
    if ([self.collectionView numberOfSections] == 0) {
        return;
    }

    NSInteger items = [self.collectionView numberOfItemsInSection:0];

    if (items == 0) {
        return;
    }

    CGFloat collectionViewContentHeight = [self.collectionView.collectionViewLayout collectionViewContentSize].height;
    BOOL isContentTooSmall = (collectionViewContentHeight < CGRectGetHeight(self.collectionView.bounds));

    if (isContentTooSmall) {
        //  workaround for the first few messages not scrolling
        //  when the collection view content size is too small, `scrollToItemAtIndexPath:` doesn't work properly
        //  this seems to be a UIKit bug, see #256 on GitHub
        [self.collectionView scrollRectToVisible:CGRectMake(0.0, collectionViewContentHeight - 1.0f, 1.0f, 1.0f)
                                        animated:animated];
        return;
    }

    //  workaround for really long messages not scrolling
    //  if last message is too long, use scroll position bottom for better appearance, else use top
    //  possibly a UIKit bug, see #480 on GitHub
    NSUInteger finalRow = MAX(0, [self.collectionView numberOfItemsInSection:0] - 1);
    NSIndexPath *finalIndexPath = [NSIndexPath indexPathForItem:finalRow inSection:0];
    CGSize finalCellSize = [self.collectionView.collectionViewLayout sizeForItemAtIndexPath:finalIndexPath];

    CGFloat maxHeightForVisibleMessage = CGRectGetHeight(self.collectionView.bounds) - self.collectionView.contentInset.top - CGRectGetHeight(self.inputToolbar.bounds);

    UICollectionViewScrollPosition scrollPosition = (finalCellSize.height > maxHeightForVisibleMessage) ? UICollectionViewScrollPositionBottom : UICollectionViewScrollPositionTop;

    [self.collectionView scrollToItemAtIndexPath:finalIndexPath atScrollPosition:scrollPosition animated:animated];
}

- (void)finishLoadingEarlierMessages:(NSInteger)loadedCount {
    // calculate offset of the top of the displayed content from the bottom of contentSize
    CGFloat bottomOffset = self.collectionView.contentSize.height - self.collectionView.contentOffset.y;
    UIImageView *snapshot = [[UIImageView alloc] init];
    [snapshot setTranslatesAutoresizingMaskIntoConstraints:false];
    [self.view insertSubview:snapshot belowSubview:self.inputToolbar];

    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:snapshot
                                                          attribute:NSLayoutAttributeCenterX
                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:self.collectionView
                                                          attribute:NSLayoutAttributeCenterX
                                                         multiplier:1
                                                           constant:0]];

    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:snapshot
                                                          attribute:NSLayoutAttributeCenterY
                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:self.collectionView
                                                          attribute:NSLayoutAttributeCenterY
                                                         multiplier:1
                                                           constant:self.collectionView.contentInset.top]];

    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:snapshot
                                                          attribute:NSLayoutAttributeWidth
                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:self.collectionView
                                                          attribute:NSLayoutAttributeWidth
                                                         multiplier:1
                                                           constant:0]];

    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:snapshot
                                                          attribute:NSLayoutAttributeHeight
                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:self.collectionView
                                                          attribute:NSLayoutAttributeHeight
                                                         multiplier:1
                                                           constant:0]];
    [self.view updateConstraints];
    [self.view layoutSubviews];

    // capture collection view image representation into UIImage
    UIGraphicsBeginImageContextWithOptions(self.collectionView.bounds.size, NO, 0);
    [self.collectionView drawViewHierarchyInRect:self.collectionView.bounds afterScreenUpdates:YES];
    UIImage *snapshotImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    snapshot.image = snapshotImage;
    snapshot.hidden = NO;
    [UIView setAnimationsEnabled:NO];

    [self.collectionView performBatchUpdates:^{
        // perform the actual insertion of new cells
        NSMutableArray *indexPaths = [NSMutableArray new];
        for (int i = 0; i < loadedCount; i++) {
            [indexPaths addObject:[NSIndexPath indexPathForItem:i inSection:0]];
        }
        [self.collectionView insertItemsAtIndexPaths:indexPaths];
    } completion:^(BOOL finished) {
        [self.collectionView.collectionViewLayout invalidateLayout];
        // after insertion finishes, scroll the collection so that content position is not
        // changed compared to such prior to the update
        self.collectionView.contentOffset = CGPointMake(0, self.collectionView.contentSize.height - bottomOffset);
        [UIView setAnimationsEnabled:YES];

        // and hide the snapshot view
        [snapshot removeFromSuperview];
    }];
}

#pragma mark - JSQMessages collection view data source

- (id<JSQMessageData>)collectionView:(JSQMessagesCollectionView *)collectionView messageDataForItemAtIndexPath:(NSIndexPath *)indexPath {
    NSAssert(NO, @"ERROR: required method not implemented: %s", __PRETTY_FUNCTION__);
    return nil;
}

- (void)collectionView:(JSQMessagesCollectionView *)collectionView didDeleteMessageAtIndexPath:(NSIndexPath *)indexPath {
    NSAssert(NO, @"ERROR: required method not implemented: %s", __PRETTY_FUNCTION__);
}

- (id<JSQMessageBubbleImageDataSource>)collectionView:(JSQMessagesCollectionView *)collectionView messageBubbleImageDataForItemAtIndexPath:(NSIndexPath *)indexPath {
    NSAssert(NO, @"ERROR: required method not implemented: %s", __PRETTY_FUNCTION__);
    return nil;
}

- (id<JSQMessageAvatarImageDataSource>)collectionView:(JSQMessagesCollectionView *)collectionView avatarImageDataForItemAtIndexPath:(NSIndexPath *)indexPath {
    NSAssert(NO, @"ERROR: required method not implemented: %s", __PRETTY_FUNCTION__);
    return nil;
}

- (NSAttributedString *)collectionView:(JSQMessagesCollectionView *)collectionView attributedTextForCellTopLabelAtIndexPath:(NSIndexPath *)indexPath {
    return nil;
}

- (NSAttributedString *)collectionView:(JSQMessagesCollectionView *)collectionView attributedTextForMessageBubbleTopLabelAtIndexPath:(NSIndexPath *)indexPath {
    return nil;
}

- (NSAttributedString *)collectionView:(JSQMessagesCollectionView *)collectionView attributedTextForCellBottomLabelAtIndexPath:(NSIndexPath *)indexPath {
    return nil;
}

#pragma mark - Collection view data source

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return 0;
}

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
    return 1;
}

- (UICollectionViewCell *)collectionView:(JSQMessagesCollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    id<JSQMessageData> messageItem = [collectionView.dataSource collectionView:collectionView messageDataForItemAtIndexPath:indexPath];
    NSParameterAssert(messageItem != nil);

    NSString *messageSenderId = [messageItem senderId];
    NSParameterAssert(messageSenderId != nil);

    BOOL isOutgoingMessage = [messageSenderId isEqualToString:self.senderId];
    BOOL isMediaMessage = [messageItem isMediaMessage];

    NSString *cellIdentifier = nil;
    JSQMessagesCollectionViewCell *cell;

    switch ([messageItem messageType]) {
    case MESSAGE_TYPE_TEXT:
    case MESSAGE_TYPE_SOS:
    case MESSAGE_TYPE_IMAGE:
    case MESSAGE_TYPE_LOCATION: {

        if (isMediaMessage) {
            cellIdentifier = isOutgoingMessage ? [JSQMessagesCollectionViewCellOutgoing mediaCellReuseIdentifier] : [JSQMessagesCollectionViewCellIncoming mediaCellReuseIdentifier];
        } else {
            cellIdentifier = isOutgoingMessage ? [JSQMessagesCollectionViewCellOutgoing cellReuseIdentifier] : [JSQMessagesCollectionViewCellIncoming cellReuseIdentifier];
        }

        cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
        cell.delegate = collectionView;

        if (!isMediaMessage) {
            if ([messageItem attributedText]) {
                cell.textView.attributedText = [messageItem attributedText];
            } else {
                cell.textView.text = [messageItem text];
            }


            if ([UIDevice jsq_isCurrentDeviceBeforeiOS8]) {
                //  workaround for iOS 7 textView data detectors bug
                if (![messageItem attributedText]) {
                    cell.textView.text = nil;
                    cell.textView.attributedText = [[NSAttributedString alloc] initWithString:[messageItem text]
                                                                                   attributes:@{ NSFontAttributeName : [collectionView.collectionViewLayout messageBubbleFontForItemAtIndexPath:indexPath] }];
                }
            }

            NSParameterAssert(cell.textView.text != nil);

            id<JSQMessageBubbleImageDataSource> bubbleImageDataSource = [collectionView.dataSource collectionView:collectionView messageBubbleImageDataForItemAtIndexPath:indexPath];
            cell.messageBubbleImageView.image = [bubbleImageDataSource messageBubbleImage];
            cell.messageBubbleImageView.highlightedImage = [bubbleImageDataSource messageBubbleHighlightedImage];
        } else {
            id<JSQMessageMediaData> messageMedia = [messageItem media];
            cell.mediaView = [messageMedia mediaView] ? : [messageMedia mediaPlaceholderView];
            NSParameterAssert(cell.mediaView != nil);
        }

        BOOL needsAvatar = YES;
        if (isOutgoingMessage && CGSizeEqualToSize(collectionView.collectionViewLayout.outgoingAvatarViewSize, CGSizeZero)) {
            needsAvatar = NO;
        } else if (!isOutgoingMessage && CGSizeEqualToSize(collectionView.collectionViewLayout.incomingAvatarViewSize, CGSizeZero)) {
            needsAvatar = NO;
        }

        id<JSQMessageAvatarImageDataSource> avatarImageDataSource = nil;
        if (needsAvatar) {
            avatarImageDataSource = [collectionView.dataSource collectionView:collectionView avatarImageDataForItemAtIndexPath:indexPath];
            if (avatarImageDataSource != nil) {

                UIImage *avatarImage = [avatarImageDataSource avatarImage];
                if (avatarImage == nil) {
                    cell.avatarImageView.image = [avatarImageDataSource avatarPlaceholderImage];
                    cell.avatarImageView.highlightedImage = nil;
                } else {
                    cell.avatarImageView.image = avatarImage;
                    cell.avatarImageView.highlightedImage = [avatarImageDataSource avatarHighlightedImage];
                }
            }
        }

        cell.cellTopLabel.attributedText = [collectionView.dataSource collectionView:collectionView attributedTextForCellTopLabelAtIndexPath:indexPath];
        cell.messageBubbleTopLabel.attributedText = [collectionView.dataSource collectionView:collectionView attributedTextForMessageBubbleTopLabelAtIndexPath:indexPath];
        cell.cellBottomLabel.attributedText = [collectionView.dataSource collectionView:collectionView attributedTextForCellBottomLabelAtIndexPath:indexPath];

        CGFloat bubbleTopLabelInset = (avatarImageDataSource != nil) ? 60.0f : 15.0f;

        if (isOutgoingMessage) {
            cell.messageBubbleTopLabel.textInsets = UIEdgeInsetsMake(0.0f, 0.0f, 0.0f, bubbleTopLabelInset);
        } else {
            cell.messageBubbleTopLabel.textInsets = UIEdgeInsetsMake(0.0f, bubbleTopLabelInset, 0.0f, 0.0f);
        }

        break;
    }
    case MESSAGE_TYPE_VOICE: {
        cellIdentifier = isOutgoingMessage ? [JSQMessagesCollectionViewCellVoiceOutgoing mediaCellReuseIdentifier] : [JSQMessagesCollectionViewCellVoiceIncoming mediaCellReuseIdentifier];
        cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
        cell.delegate = collectionView;

        BOOL needsAvatar = YES;
        if (isOutgoingMessage && CGSizeEqualToSize(collectionView.collectionViewLayout.outgoingAvatarViewSize, CGSizeZero)) {
            needsAvatar = NO;
        } else if (!isOutgoingMessage && CGSizeEqualToSize(collectionView.collectionViewLayout.incomingAvatarViewSize, CGSizeZero)) {
            needsAvatar = NO;
        }

        JSQMessagesCollectionViewVoiceCell *voiceCell = (JSQMessagesCollectionViewVoiceCell *)cell;
        voiceCell.voiceSeconds = [messageItem mediaDuration];

        id<JSQMessageBubbleImageDataSource> bubbleImageDataSource = [collectionView.dataSource collectionView:collectionView messageBubbleImageDataForItemAtIndexPath:indexPath];
        if (bubbleImageDataSource != nil) {
            cell.messageBubbleImageView.image = [bubbleImageDataSource messageBubbleImage];
            cell.messageBubbleImageView.highlightedImage = [bubbleImageDataSource messageBubbleHighlightedImage];
        }

        id<JSQMessageAvatarImageDataSource> avatarImageDataSource = nil;
        if (needsAvatar) {
            avatarImageDataSource = [collectionView.dataSource collectionView:collectionView avatarImageDataForItemAtIndexPath:indexPath];
            if (avatarImageDataSource != nil) {

                UIImage *avatarImage = [avatarImageDataSource avatarImage];
                if (avatarImage == nil) {
                    cell.avatarImageView.image = [avatarImageDataSource avatarPlaceholderImage];
                    cell.avatarImageView.highlightedImage = nil;
                } else {
                    cell.avatarImageView.image = avatarImage;
                    cell.avatarImageView.highlightedImage = [avatarImageDataSource avatarHighlightedImage];
                }
            }
        }


        cell.cellTopLabel.attributedText = [collectionView.dataSource collectionView:collectionView attributedTextForCellTopLabelAtIndexPath:indexPath];
        cell.messageBubbleTopLabel.attributedText = [collectionView.dataSource collectionView:collectionView attributedTextForMessageBubbleTopLabelAtIndexPath:indexPath];
        cell.cellBottomLabel.attributedText = [collectionView.dataSource collectionView:collectionView attributedTextForCellBottomLabelAtIndexPath:indexPath];

        CGFloat bubbleTopLabelInset = (avatarImageDataSource != nil) ? 60.0f : 15.0f;

        if (isOutgoingMessage) {
            cell.messageBubbleTopLabel.textInsets = UIEdgeInsetsMake(0.0f, 0.0f, 0.0f, bubbleTopLabelInset);
        } else {
            cell.messageBubbleTopLabel.textInsets = UIEdgeInsetsMake(0.0f, bubbleTopLabelInset, 0.0f, 0.0f);
        }
        break;
    }
    case MESSAGE_TYPE_GROUP_JOIN:
    case MESSAGE_TYPE_GROUP_LEAVE: {
        cellIdentifier = [JSQMessagesCollectionViewCellNotice cellReuseIdentifier];
        cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
        cell.delegate = collectionView;

        cell.textView.backgroundColor = [UIColor colorWithWhite:0.565 alpha:1.000];
        cell.textView.layer.masksToBounds = YES;
        cell.textView.layer.cornerRadius = 8.0;

        cell.textView.font = [collectionView.collectionViewLayout messageBubbleFontForItemAtIndexPath:indexPath];
        if ([messageItem attributedText]) {
            cell.textView.attributedText = [messageItem attributedText];
        } else {
            cell.textView.text = [messageItem text];
        }

        if ([UIDevice jsq_isCurrentDeviceBeforeiOS8]) {
            //  workaround for iOS 7 textView data detectors bug
            cell.textView.text = nil;
            cell.textView.attributedText = [[NSAttributedString alloc] initWithString:[messageItem text]
                                                                           attributes:@{ NSFontAttributeName : [collectionView.collectionViewLayout messageBubbleFontForItemAtIndexPath:indexPath] }];

        }


        cell.textView.font = [collectionView.collectionViewLayout messageBubbleFontForItemAtIndexPath:indexPath];
        cell.textView.textAlignment = NSTextAlignmentCenter;

        cell.textView.linkTextAttributes = @{ NSForegroundColorAttributeName : [UIColor colorWithWhite:0.166 alpha:1.000],
                                              NSUnderlineStyleAttributeName : @(NSUnderlineStyleSingle | NSUnderlinePatternSolid),
                                              NSFontAttributeName : [collectionView.collectionViewLayout messageBubbleFontForItemAtIndexPath:indexPath]};

        NSParameterAssert(cell.textView.text != nil);

        cell.textView.textColor = [UIColor colorWithWhite:0.166 alpha:1.000];
        cell.cellTopLabel.attributedText = [collectionView.dataSource collectionView:collectionView attributedTextForCellTopLabelAtIndexPath:indexPath];
        cell.messageBubbleTopLabel.attributedText = [collectionView.dataSource collectionView:collectionView attributedTextForMessageBubbleTopLabelAtIndexPath:indexPath];
        cell.cellBottomLabel.attributedText = [collectionView.dataSource collectionView:collectionView attributedTextForCellBottomLabelAtIndexPath:indexPath];
        break;
    }
    default:
        break;
    }

    switch ([messageItem messageStatus]) {
    case MESSAGE_STATE_SEND_FAIL:
    case MESSAGE_STATE_DOWNLOAD_FAIL: {
        cell.retryMessage.hidden = NO;
        break;
    }

    default: cell.retryMessage.hidden = YES;
        break;
    }

    cell.textView.dataDetectorTypes = UIDataDetectorTypeAll;

    cell.backgroundColor = [UIColor clearColor];
    cell.layer.rasterizationScale = [UIScreen mainScreen].scale;
    cell.layer.shouldRasterize = YES;

    return cell;
}

- (UICollectionReusableView *)collectionView:(JSQMessagesCollectionView *)collectionView
           viewForSupplementaryElementOfKind:(NSString *)kind
                                 atIndexPath:(NSIndexPath *)indexPath {
    if (self.showTypingIndicator && [kind isEqualToString:UICollectionElementKindSectionFooter]) {
        return [collectionView dequeueTypingIndicatorFooterViewForIndexPath:indexPath];
    } else if (self.showLoadEarlierMessagesHeader && [kind isEqualToString:UICollectionElementKindSectionHeader]) {
        return [collectionView dequeueLoadEarlierMessagesViewHeaderForIndexPath:indexPath];
    }

    return nil;
}

- (CGSize)collectionView:(UICollectionView *)collectionView
                  layout:(JSQMessagesCollectionViewFlowLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section {
    if (!self.showTypingIndicator) {
        return CGSizeZero;
    }

    return CGSizeMake([collectionViewLayout itemWidth], kJSQMessagesTypingIndicatorFooterViewHeight);
}

- (CGSize)collectionView:(UICollectionView *)collectionView
                  layout:(JSQMessagesCollectionViewFlowLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section {
    if (!self.showLoadEarlierMessagesHeader) {
        return CGSizeZero;
    }

    return CGSizeMake([collectionViewLayout itemWidth], kJSQMessagesLoadEarlierHeaderViewHeight);
}

#pragma mark - Collection view delegate

- (BOOL)collectionView:(JSQMessagesCollectionView *)collectionView shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath {
    //  disable menu for media messages
    //    id<JSQMessageData> messageItem = [collectionView.dataSource collectionView:collectionView messageDataForItemAtIndexPath:indexPath];
    //    if ([messageItem isMediaMessage]) {
    //        return NO;
    //    }

    self.selectedIndexPathForMenu = indexPath;

    //  textviews are selectable to allow data detectors
    //  however, this allows the 'copy, define, select' UIMenuController to show
    //  which conflicts with the collection view's UIMenuController
    //  temporarily disable 'selectable' to prevent this issue
    JSQMessagesCollectionViewCell *selectedCell = (JSQMessagesCollectionViewCell *)[collectionView cellForItemAtIndexPath:indexPath];
    selectedCell.textView.selectable = NO;

    return YES;
}

- (BOOL)collectionView:(JSQMessagesCollectionView *)collectionView canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
    MenuType menuType = [collectionView.delegate collectionViewMenu:collectionView messageBubbleAtIndexPath:indexPath];

    if ((menuType & MenuTypeNone)) {
        return NO;
    } else {
        if ((menuType & MenuTypeCollection)) {
            if (action == @selector(collection:forItemAtIndexPath:)) {
                return YES;
            }
        }
        if ((menuType & MenuTypeDelete)) {
            if (action == @selector(customDelete:forItemAtIndexPath:)) {
                return YES;
            }
        }
        if ((menuType & MenuTypeForward)) {
            if (action == @selector(forward:forItemAtIndexPath:)) {
                return YES;
            }
        }
        if ((menuType & MenuTypeRefresh)) {
            if (action == @selector(refresh:forItemAtIndexPath:)) {
                return YES;
            }
        }
        if ((menuType & MenuTypeCopy)) {
            if (action == @selector(customCopy:)) {
                return YES;
            }
        }
    }

    return NO;

}

- (void)collectionView:(JSQMessagesCollectionView *)collectionView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
    id<JSQMessageData> messageData = [collectionView.dataSource collectionView:collectionView messageDataForItemAtIndexPath:indexPath];
    if (action == @selector(copy:)) {
        [[UIPasteboard generalPasteboard] setString:[messageData text]];
    } else if (action == @selector(delete:)) {
        [collectionView.dataSource collectionView:collectionView didDeleteMessageAtIndexPath:indexPath];
        [collectionView deleteItemsAtIndexPaths:@[indexPath]];
        [collectionView.collectionViewLayout invalidateLayout];
    } else if (action == @selector(customCopy:)) {
        [self customCopy:messageData];
    } else if (action == @selector(customDelete:forItemAtIndexPath:)) {
        [self customDelete:messageData forItemAtIndexPath:indexPath];
    } else if (action == @selector(collection:forItemAtIndexPath:)) {
        [self collection:messageData forItemAtIndexPath:indexPath];
    } else if (action == @selector(forward:forItemAtIndexPath:)) {
        [self forward:messageData forItemAtIndexPath:indexPath];
    } else if (action == @selector(refresh:forItemAtIndexPath:)) {
        [self refresh:messageData forItemAtIndexPath:indexPath];
    }
}

#pragma mark - Collection view delegate flow layout

- (CGSize)collectionView:(JSQMessagesCollectionView *)collectionView layout:(JSQMessagesCollectionViewFlowLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
    return [collectionViewLayout sizeForItemAtIndexPath:indexPath];
}

- (CGFloat)collectionView:(JSQMessagesCollectionView *)collectionView layout:(JSQMessagesCollectionViewFlowLayout *)collectionViewLayout heightForCellTopLabelAtIndexPath:(NSIndexPath *)indexPath {
    return 0.0f;
}

- (CGFloat)collectionView:(JSQMessagesCollectionView *)collectionView layout:(JSQMessagesCollectionViewFlowLayout *)collectionViewLayout heightForMessageBubbleTopLabelAtIndexPath:(NSIndexPath *)indexPath {
    return 0.0f;
}

- (CGFloat)collectionView:(JSQMessagesCollectionView *)collectionView layout:(JSQMessagesCollectionViewFlowLayout *)collectionViewLayout heightForCellBottomLabelAtIndexPath:(NSIndexPath *)indexPath {
    return 0.0f;
}

- (UIFont *)messageBubbleFontForMessageData:(id<JSQMessageData>)messageData
                                atIndexPath:(NSIndexPath *)indexPath
                                 withLayout:(JSQMessagesCollectionViewFlowLayout *)layout {
    return [self messageBubbleFontAtMessageData:messageData atIndexPath:indexPath];
}

- (void)collectionView:(JSQMessagesCollectionView *)collectionView didTapAvatarImageView:(UIImageView *)avatarImageView atIndexPath:(NSIndexPath *)indexPath {

}

- (void)collectionView:(JSQMessagesCollectionView *)collectionView didTapReSendingMessageBubbleAtIndexPath:(NSIndexPath *)indexPath {
    [self reSending:indexPath];
}

- (void)collectionView:(JSQMessagesCollectionView *)collectionView didTapMessageBubbleAtIndexPath:(NSIndexPath *)indexPath {
    id<JSQMessageData> messageData = [collectionView.dataSource collectionView:collectionView messageDataForItemAtIndexPath:indexPath];

    switch ([messageData messageType]) {
    case MESSAGE_TYPE_TEXT: {

        break;
    }
    case MESSAGE_TYPE_IMAGE: {

        break;
    }
    case MESSAGE_TYPE_VOICE: {

        JSQMessagesCollectionViewVoiceCell *collectionCell = (JSQMessagesCollectionViewVoiceCell *)[self.collectionView cellForItemAtIndexPath:indexPath];

        if ([JSQMessageTools isFilePath:[messageData fileLocalPath]]) {
            [_audioAnimation playFilePath:[NSURL fileURLWithPath:[messageData fileLocalPath]] animation:collectionCell];
        }


        break;
    }
    case MESSAGE_TYPE_LOCATION: {

        break;
    }
    case MESSAGE_TYPE_SOS: {

        break;
    }
    default:
        break;
    }
}

- (void)collectionView:(JSQMessagesCollectionView *)collectionView didTapCellAtIndexPath:(NSIndexPath *)indexPath touchLocation:(CGPoint)touchLocation {

}

#pragma mark - Input toolbar delegate

- (void)messagesInputToolbar:(JSQMessagesInputToolbar *)toolbar didPressLeftBarButton:(UIButton *)sender {
    [self didPressLeftAccessoryButton:sender];
}

- (void)messagesInputToolbar:(JSQMessagesInputToolbar *)toolbar didPressRightOtherBarButton:(UIButton *)sender {
    [self didPressRightOtherAccessoryButton:sender];
}

- (void)messagesInputToolbar:(JSQMessagesInputToolbar *)toolbar didPressRightFaceBarButton:(UIButton *)sender {
    [self didPressRightFaceAccessoryButton:sender];
}

- (NSString *)jsq_currentlyComposedMessageText {
    if (self.inputToolbar.contentView.textView.attributedText) {
        return [self.inputToolbar.contentView.textView.textStorage plainEmotionString];
    }
    //  auto-accept any auto-correct suggestions
    [self.inputToolbar.contentView.textView.inputDelegate selectionWillChange:self.inputToolbar.contentView.textView];
    [self.inputToolbar.contentView.textView.inputDelegate selectionDidChange:self.inputToolbar.contentView.textView];

    return [self.inputToolbar.contentView.textView.text jsq_stringByTrimingWhitespace];
}

#pragma mark - Text view delegate

- (void)textViewDidBeginEditing:(UITextView *)textView {
    if (textView != self.inputToolbar.contentView.textView) {
        return;
    }

    [self.inputToolbar.contentView.leftBarButtonItem setImage:[UIImage jsq_bubbleVoiceImage] forState:UIControlStateNormal];
    [self.inputToolbar.contentView.rightFaceBarButtonItem setImage:[UIImage jsq_bubbleFaceImage] forState:UIControlStateNormal];
    [self.inputToolbar.contentView.rightOtherBarButtonItem setImage:[UIImage jsq_bubbleOtherImage] forState:UIControlStateNormal];

    if (self.automaticallyScrollsToMostRecentMessage) {
        [self scrollToBottomAnimated:YES];
    }
}

- (void)textViewDidChange:(UITextView *)textView {
    if (textView != self.inputToolbar.contentView.textView) {
        return;
    }

}

- (void)textViewDidEndEditing:(UITextView *)textView {
    if (textView != self.inputToolbar.contentView.textView) {
        return;
    }

    [textView resignFirstResponder];
}

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
    if ([text isEqualToString:@"\n"]) {

        JSQMessageModel *messageModel = [self createBasicMessages];
        messageModel.text = [self jsq_currentlyComposedMessageText];
        messageModel.attributedText = textView.attributedText;
        messageModel.senderId = self.senderId;
        messageModel.senderDisplayName = self.senderDisplayName;
        messageModel.date = [NSDate date];

        [self didPressSendButton:nil withMessageModel:messageModel withMessageMediaData:nil];

        return NO;
    }
    return YES;
}

#pragma mark - Notifications

- (void)jsq_handleDidChangeStatusBarFrameNotification:(NSNotification *)notification {
    if (self.keyboardController.keyboardIsVisible) {
        [self jsq_setToolbarBottomLayoutGuideConstant:CGRectGetHeight(self.keyboardController.currentKeyboardFrame)];
    }
}

- (void)jsq_didReceiveMenuWillShowNotification:(NSNotification *)notification {
    if (!self.selectedIndexPathForMenu) {
        return;
    }

    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:UIMenuControllerWillShowMenuNotification
                                                  object:nil];

    UIMenuController *menu = [notification object];
    [menu setMenuVisible:NO animated:NO];

    JSQMessagesCollectionViewCell *selectedCell = (JSQMessagesCollectionViewCell *)[self.collectionView cellForItemAtIndexPath:self.selectedIndexPathForMenu];
    CGRect selectedCellMessageBubbleFrame = [selectedCell convertRect:selectedCell.messageBubbleContainerView.frame toView:self.view];

    [menu setTargetRect:selectedCellMessageBubbleFrame inView:self.view];
    [menu setMenuVisible:YES animated:YES];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(jsq_didReceiveMenuWillShowNotification:)
                                                 name:UIMenuControllerWillShowMenuNotification
                                               object:nil];
}

- (void)jsq_didReceiveMenuWillHideNotification:(NSNotification *)notification {
    if (!self.selectedIndexPathForMenu) {
        return;
    }

    //  per comment above in 'shouldShowMenuForItemAtIndexPath:'
    //  re-enable 'selectable', thus re-enabling data detectors if present
    JSQMessagesCollectionViewCell *selectedCell = (JSQMessagesCollectionViewCell *)[self.collectionView cellForItemAtIndexPath:self.selectedIndexPathForMenu];
    selectedCell.textView.selectable = YES;
    self.selectedIndexPathForMenu = nil;
}

#pragma mark - Key-value observing

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if (context == kJSQMessagesKeyValueObservingContext) {

        if (object == self.inputToolbar.contentView.textView
            && [keyPath isEqualToString:NSStringFromSelector(@selector(contentSize))]) {

            CGSize oldContentSize = [[change objectForKey:NSKeyValueChangeOldKey] CGSizeValue];
            CGSize newContentSize = [[change objectForKey:NSKeyValueChangeNewKey] CGSizeValue];

            CGFloat dy = newContentSize.height - oldContentSize.height;

            [self jsq_adjustInputToolbarForComposerTextViewContentSizeChange:dy];
            [self jsq_updateCollectionViewInsets];
            if (self.automaticallyScrollsToMostRecentMessage) {
                [self scrollToBottomAnimated:NO];
            }
        }
    }
}

#pragma mark - Keyboard controller delegate

- (void)keyboardController:(JSQMessagesKeyboardController *)keyboardController keyboardDidChangeFrame:(CGRect)keyboardFrame {
    if (![self.inputToolbar.contentView.textView isFirstResponder] && self.toolbarBottomLayoutGuide.constant == self.offsetHeight) {
        return;
    }

    CGFloat heightFromBottom = CGRectGetMaxY(self.collectionView.frame) - CGRectGetMinY(keyboardFrame);

    heightFromBottom = MAX(self.offsetHeight, heightFromBottom);

    [self jsq_setToolbarBottomLayoutGuideConstant:heightFromBottom];

    if (!keyboardController.jsq_isObserving) {
        self.offsetHeight = 0.0f;
    }else {
        [self scrollToBottomAnimated:YES];
    }
}

- (void)jsq_setToolbarBottomLayoutGuideConstant:(CGFloat)constant {
    self.toolbarBottomLayoutGuide.constant = constant;
    [self.view setNeedsUpdateConstraints];
    [self.view layoutIfNeeded];

    [self jsq_updateCollectionViewInsets];
}

- (void)setToolbarBottomLayoutGuideConstant:(CGFloat)constant {
    self.offsetHeight = constant;
    [self.inputToolbar.contentView.textView resignFirstResponder];
    [self jsq_setToolbarBottomLayoutGuideConstant:constant];
}

- (void)jsq_updateKeyboardTriggerPoint {
    self.keyboardController.keyboardTriggerPoint = CGPointMake(0.0f, CGRectGetHeight(self.inputToolbar.bounds));
}

#pragma mark - Gesture recognizers

- (void)jsq_handleInteractivePopGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer {
    switch (gestureRecognizer.state) {
    case UIGestureRecognizerStateBegan:
    {
        if ([UIDevice jsq_isCurrentDeviceBeforeiOS8]) {
            [self.snapshotView removeFromSuperview];
        }

        self.textViewWasFirstResponderDuringInteractivePop = [self.inputToolbar.contentView.textView isFirstResponder];

        [self.keyboardController endListeningForKeyboard];

        if ([UIDevice jsq_isCurrentDeviceBeforeiOS8]) {
            [self.inputToolbar.contentView.textView resignFirstResponder];
            [UIView animateWithDuration:0.0
                             animations:^{
                    [self jsq_setToolbarBottomLayoutGuideConstant:0.0f];
                }];

            UIView *snapshot = [self.view snapshotViewAfterScreenUpdates:YES];
            [self.view addSubview:snapshot];
            self.snapshotView = snapshot;
        }
        break;
    }
    case UIGestureRecognizerStateChanged:
        break;
    case UIGestureRecognizerStateCancelled:
    case UIGestureRecognizerStateEnded:
    case UIGestureRecognizerStateFailed:
        [self.keyboardController beginListeningForKeyboard];
        if (self.textViewWasFirstResponderDuringInteractivePop) {
            [self.inputToolbar.contentView.textView resignFirstResponder];
        }

        if ([UIDevice jsq_isCurrentDeviceBeforeiOS8]) {
            [self.snapshotView removeFromSuperview];
        }
        break;
    default:
        break;
    }
}

#pragma mark - Input toolbar utilities

- (BOOL)jsq_inputToolbarHasReachedMaximumHeight {
    return CGRectGetMinY(self.inputToolbar.frame) == (self.topLayoutGuide.length + self.topContentAdditionalInset);
}

- (void)jsq_adjustInputToolbarForComposerTextViewContentSizeChange:(CGFloat)dy {
    BOOL contentSizeIsIncreasing = (dy > 0);

    if ([self jsq_inputToolbarHasReachedMaximumHeight]) {
        BOOL contentOffsetIsPositive = (self.inputToolbar.contentView.textView.contentOffset.y > 0);

        if (contentSizeIsIncreasing || contentOffsetIsPositive) {
            [self jsq_scrollComposerTextViewToBottomAnimated:YES];
            return;
        }
    }

    CGFloat toolbarOriginY = CGRectGetMinY(self.inputToolbar.frame);
    CGFloat newToolbarOriginY = toolbarOriginY - dy;

    //  attempted to increase origin.Y above topLayoutGuide
    if (newToolbarOriginY <= self.topLayoutGuide.length + self.topContentAdditionalInset) {
        dy = toolbarOriginY - (self.topLayoutGuide.length + self.topContentAdditionalInset);
        [self jsq_scrollComposerTextViewToBottomAnimated:YES];
    }

    [self jsq_adjustInputToolbarHeightConstraintByDelta:dy];

    [self jsq_updateKeyboardTriggerPoint];

    if (dy < 0) {
        [self jsq_scrollComposerTextViewToBottomAnimated:NO];
    }
}

- (void)jsq_adjustInputToolbarHeightConstraintByDelta:(CGFloat)dy {
    CGFloat proposedHeight = self.toolbarHeightConstraint.constant + dy;

    CGFloat finalHeight = MAX(proposedHeight, self.inputToolbar.preferredDefaultHeight);

    if (self.inputToolbar.maximumHeight != NSNotFound) {
        finalHeight = MIN(finalHeight, self.inputToolbar.maximumHeight);
    }

    if (self.toolbarHeightConstraint.constant != finalHeight) {
        self.toolbarHeightConstraint.constant = finalHeight;
        [self.view setNeedsUpdateConstraints];
        [self.view layoutIfNeeded];
    }
}

- (void)jsq_scrollComposerTextViewToBottomAnimated:(BOOL)animated {
    UITextView *textView = self.inputToolbar.contentView.textView;
    CGPoint contentOffsetToShowLastLine = CGPointMake(0.0f, textView.contentSize.height - CGRectGetHeight(textView.bounds));

    if (!animated) {
        textView.contentOffset = contentOffsetToShowLastLine;
        return;
    }

    [UIView animateWithDuration:0.01
                          delay:0.01
                        options:UIViewAnimationOptionCurveLinear
                     animations:^{
        textView.contentOffset = contentOffsetToShowLastLine;
    }
                     completion:nil];
}

#pragma mark - Collection view utilities

- (void)jsq_updateCollectionViewInsets {
    [self jsq_setCollectionViewInsetsTopValue:self.topLayoutGuide.length + self.topContentAdditionalInset
                                  bottomValue:CGRectGetMaxY(self.collectionView.frame) - CGRectGetMinY(self.inputToolbar.frame)];
}

- (void)jsq_setCollectionViewInsetsTopValue:(CGFloat)top bottomValue:(CGFloat)bottom {
    UIEdgeInsets insets = UIEdgeInsetsMake(top, 0.0f, bottom, 0.0f);
    self.collectionView.contentInset = insets;
    self.collectionView.scrollIndicatorInsets = insets;
//    [self scrollToBottomAnimated:YES];
}

- (BOOL)jsq_isMenuVisible {
    //  check if cell copy menu is showing
    //  it is only our menu if `selectedIndexPathForMenu` is not `nil`
    return self.selectedIndexPathForMenu != nil && [[UIMenuController sharedMenuController] isMenuVisible];
}

#pragma mark - Utilities

- (void)jsq_addObservers {
    if (self.jsq_isObserving) {
        return;
    }

    [self.inputToolbar.contentView.textView addObserver:self
                                             forKeyPath:NSStringFromSelector(@selector(contentSize))
                                                options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
                                                context:kJSQMessagesKeyValueObservingContext];

    self.jsq_isObserving = YES;
}

- (void)jsq_removeObservers {
    if (!_jsq_isObserving) {
        return;
    }

    @try {
        [_inputToolbar.contentView.textView removeObserver:self
                                                forKeyPath:NSStringFromSelector(@selector(contentSize))
                                                   context:kJSQMessagesKeyValueObservingContext];
    }@catch (NSException *__unused exception) {
    }

    _jsq_isObserving = NO;
}

- (void)jsq_registerForNotifications:(BOOL)registerForNotifications {
    if (registerForNotifications) {
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(jsq_handleDidChangeStatusBarFrameNotification:)
                                                     name:UIApplicationDidChangeStatusBarFrameNotification
                                                   object:nil];

        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(jsq_didReceiveMenuWillShowNotification:)
                                                     name:UIMenuControllerWillShowMenuNotification
                                                   object:nil];

        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(jsq_didReceiveMenuWillHideNotification:)
                                                     name:UIMenuControllerWillHideMenuNotification
                                                   object:nil];
    } else {
        [[NSNotificationCenter defaultCenter] removeObserver:self
                                                        name:UIApplicationDidChangeStatusBarFrameNotification
                                                      object:nil];

        [[NSNotificationCenter defaultCenter] removeObserver:self
                                                        name:UIMenuControllerWillShowMenuNotification
                                                      object:nil];

        [[NSNotificationCenter defaultCenter] removeObserver:self
                                                        name:UIMenuControllerWillHideMenuNotification
                                                      object:nil];
    }
}

- (void)jsq_addActionToInteractivePopGestureRecognizer:(BOOL)addAction {
    if (self.currentInteractivePopGestureRecognizer != nil) {
        [self.currentInteractivePopGestureRecognizer removeTarget:nil
                                                           action:@selector(jsq_handleInteractivePopGestureRecognizer:)];
        self.currentInteractivePopGestureRecognizer = nil;
    }

    if (addAction) {
        [self.navigationController.interactivePopGestureRecognizer addTarget:self
                                                                      action:@selector(jsq_handleInteractivePopGestureRecognizer:)];
        self.currentInteractivePopGestureRecognizer = self.navigationController.interactivePopGestureRecognizer;
    }
}

- (void)sendImages:(NSArray *)images {

}

- (void)sendVoice:(JSQMessageModel *)messageMode {

}

- (void)sendLocal:(JSQMessageModel *)messageModel mapLocation:(NSString *)title location:(CLLocation *)location {

}

@end
